package org.smile.tomcat.session.redis;

import java.io.IOException;
import java.lang.reflect.Constructor;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Loader;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.Valve;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.smile.tomcat.session.JdkSerializer;
import org.smile.tomcat.session.Serializer;
import org.smile.tomcat.session.SessionIdGenerator;
import org.smile.tomcat.session.StoreManager;
import org.smile.tomcat.session.UuidSessionIdGenerator;

/**
 * 使用redis保存session数据
 * 
 * @author 胡真山
 *
 */
public class RedisManager extends StoreManager {
	/**日志*/
	protected final Log logger = LogFactory.getLog(RedisManager.class);
	/** 对象序列化 */
	protected Serializer<Object> serializer=new JdkSerializer();
	/**redis操作模板*/
	protected RedisTemplate redisTemplate=new RedisTemplate();
	/**存贮实现器*/
	protected RedisStore redisStore=new RedisStore();
	/**用于生成session id*/
	protected SessionIdGenerator generator=new UuidSessionIdGenerator();
	
	protected RedisSessionHandleValve handleValve;
	/**线程对象保存当前session*/
	protected ThreadLocal<RedisSession> currentSessions=new InheritableThreadLocal<RedisSession>();
	/***以下是配置信息*/
	protected String host;
	/**端口*/
	protected Integer port;
	/**数据库索引*/
	protected Integer database;
	/**数据库密码*/
	protected String password;
	/**连接超时时间*/
	protected Integer timeout;
	/**redis最大连接数*/
	protected Integer maxTotal;
	/**redis最大空闲连接数*/
	protected Integer maxIdle;
	/**redis最小空闲连接*/
	protected Integer minIdle;
	/**哨兵配置*/
	protected String sentinelSets;
	/**集群时使用,哨兵模式 主节点*/
	protected String sentinelMaster;
	/**可自定义序列化类*/
	protected String serializerClass;
	/***
	 * session实现类
	 */
	protected Class<RedisSession> sessionClass=RedisSession.class;

	public void setSerializer(Serializer<Object> serializer) {
		this.serializer = serializer;
	}

	public void setRedisTemplate(RedisTemplate redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	@Override
	public void processExpires() {
		// We are going to use Redis's ability to expire keys for session
		// expiration.
	}

	@Override
	public void remove(Session session) {
		remove(session, false);
	}

	@Override
	public void remove(Session session, boolean update) {
		String sessionId = session.getId();
		super.remove(session, update);
		this.redisStore.remove(sessionId);
	}

	@Override
	public Session createEmptySession() {
		try{
			Constructor<RedisSession> constructor= sessionClass.getConstructor(Manager.class);
			RedisSession session=constructor.newInstance(this);
			session.setStore(redisStore);
			session.tellNew();
			return session;
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}

	@Override
	public void add(Session session) {
		if (session instanceof RedisSession) {
			RedisSession redisSession=(RedisSession) session;
			currentSessions.set(redisSession);
			this.redisStore.save((RedisSession) session);
		}
	}

	@Override
	public Session findSession(String id) throws IOException {
		Session session = currentSessions.get();
		if (session != null&&session.isValid()) {
			return session;
		} else {
			RedisSession redisSession = this.redisStore.load(id);
			if (redisSession != null) {
				currentSessions.set(redisSession);
				return redisSession;
			}
		}
		return session;
	}

	@Override
	protected synchronized void startInternal() throws LifecycleException {
		super.startInternal();
		this.initConfig();
		this.redisStore.setManager(this);
		//设置缓存过期时间
		this.redisStore.setExpireTime(getMaxSessionTimeout()+60);
		this.redisTemplate.init();
		this.redisStore.setRedisTemplate(redisTemplate);
		this.redisStore.setSerializer(serializer);
		Loader loader=context().getLoader();
		if(loader!=null){
			serializer.setLoader(loader.getClassLoader());
		}
		setState(LifecycleState.STARTING);
		Boolean attachedToValve = false;
		for (Valve valve : context().getPipeline().getValves()) {
			if (valve instanceof RedisSessionHandleValve) {
				this.handleValve = (RedisSessionHandleValve) valve;
				this.handleValve.setRedisSessionManager(this);
				logger.info("Attached to RedisSessionHandlerValve");
				attachedToValve = true;
				break;
			}
		}
		if (!attachedToValve) {
			String error = "Unable to attach to session handling valve; sessions cannot be saved after the request without the valve starting properly.";
			logger.fatal(error);
			throw new LifecycleException(error);
		}
	}
	/**
	 * 初始化配置信息
	 */
	private void initConfig() {
		if(this.host!=null){
			this.redisTemplate.setHost(host);
		}
		if(this.port!=null){
			this.redisTemplate.setPort(port);
		}
		if(this.password!=null){
			this.redisTemplate.setPassword(password);
		}
		if(this.timeout!=null){
			this.redisTemplate.setTimeout(timeout);
		}
		if(this.database!=null){
			this.redisTemplate.setDatabase(database);
		}
		if(this.sentinelSets!=null){
			this.redisTemplate.setSentinelSets(sentinelSets);
		}
		if(this.sentinelMaster!=null){
			this.redisTemplate.setSentinelMaster(sentinelMaster);
		}
		if(this.maxTotal!=null){
			this.redisTemplate.redisPoolConfig.setMaxTotal(maxTotal);
		}
		if(this.maxIdle!=null){
			this.redisTemplate.redisPoolConfig.setMaxIdle(maxIdle);
		}
		if(this.minIdle!=null){
			this.redisTemplate.redisPoolConfig.setMinIdle(minIdle);
		}
	}

	@Override
	protected synchronized void stopInternal() throws LifecycleException {
		if (logger.isDebugEnabled()) {
			logger.debug("Stopping");
		}
		setState(LifecycleState.STOPPING);
		try {
			this.redisTemplate.destroy();
		} catch (Exception e) {
			logger.error(e);
		}
		super.stopInternal();
	}

	@Override
	public Session createSession(String requestedSessionId) {
		RedisSession session = null;
		String sessionId = null;
		String jvmRoute = getJvmRoute();
		// Ensure generation of a unique session identifier.
		if (null != requestedSessionId) {
			sessionId = sessionIdWithJvmRoute(requestedSessionId, jvmRoute);
		} else {
			sessionId = sessionIdWithJvmRoute(generateSessionId(), jvmRoute);
		}
		if (null != sessionId) {
			session = (RedisSession) createEmptySession();
			session.setNew(true);
			session.setValid(true);
			session.setCreationTime(System.currentTimeMillis());
			session.setMaxInactiveInterval(getSessionMaxAliveTime());
			session.setId(sessionId);
			session.tellNew();
			this.redisStore.save(session);
			this.currentSessions.set(session);
		}
		return session;
	}

	private String sessionIdWithJvmRoute(String sessionId, String jvmRoute) {
		if (jvmRoute != null) {
			String jvmRoutePrefix = '.' + jvmRoute;
			return sessionId.endsWith(jvmRoutePrefix) ? sessionId : sessionId + jvmRoutePrefix;
		}
		return sessionId;
	}

	/**
	 * 一次请求完成时调用
	 * 可清理当前线程变量中的信息
	 */
	public void afterRequest() {
		RedisSession session=this.currentSessions.get();
		if(session!=null){
			session.afterRequest();
			this.currentSessions.remove();
		}
	}

	/**
	 * Generate and return a new session identifier.
	 */
	protected String generateSessionId() {
		String result = this.generator.generate();
		return result;
	}

	

	public void setRedisStore(RedisStore redisStore) {
		this.redisStore = redisStore;
	}

	public void setGenerator(SessionIdGenerator generator) {
		this.generator = generator;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public void setPort(Integer port) {
		this.port = port;
	}

	public void setDatabase(Integer database) {
		this.database = database;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setTimeout(Integer timeout) {
		this.timeout = timeout;
	}

	public void setSentinelSets(String sentinelSets) {
		this.sentinelSets = sentinelSets;
	}

	public void setSerializerClass(String serializerClass) {
		this.serializerClass = serializerClass;
	}

	public void setSentinelMaster(String sentinelMaster) {
		this.sentinelMaster = sentinelMaster;
	}
	
	public void setSessionClass(String sessionClass){
		try {
			this.sessionClass=(Class<RedisSession>)Class.forName(sessionClass);
		} catch (ClassNotFoundException e) {
			logger.error("初始化session实现类失败",e);
		}
	}

	public void setMaxTotal(Integer maxTotal) {
		this.maxTotal = maxTotal;
	}

	public void setMaxIdle(Integer maxIdle) {
		this.maxIdle = maxIdle;
	}

	public void setMinIdle(Integer minIdle) {
		this.minIdle = minIdle;
	}
	
	
	
}
